package com.gitee.l0km.aocache.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.Properties;

import com.gitee.l0km.aocache.AocacheLogger;

/**
 * AoCache全局配置类，负责加载和管理缓存框架的全局配置。<br>
 * <br>
 * <p><b>配置加载顺序：</b><br>
 * 1. 首先从默认路径${user.home}/.aocache/aoconfig.properties加载<br>
 * 2. 然后尝试从环境变量AOCACHE_CONFIG_PROPERTIES指定的文件加载<br>
 * <br>
 * <p><b>配置项说明：</b><br>
 * <ul>
 * <li> debugOutput: 是否启用调试输出(默认false)</li>
 * <li> debugOutputDetail: 是否输出详细调试信息(默认false)</li>
 * <li> outputAroundCost: 是否输出环绕方法耗时(默认false)</li>
 * <li> override: 是否启用注解定义的日志配置覆盖全局debugOutput，debugOutputDetail，outputAroundCost定义(默认false)</li>
 * <li> usingLoadedJoinPoints: 是否使用加载的切入点配置(默认false)</li>
 * </ul>
 * <br>
 * <p><b>注意：</b><br>
 * <ul>
 * <li> 环境变量指定的配置文件优先级高于默认配置文件</li>
 * <li> 如果两个配置文件中存在相同配置项，后加载的会覆盖先加载的值</li>
 * </ul>
 * @author guyadong
 */
public class AoCacheGlobalConfig {
    private static final String DEFAULT_CONFIG_PATH = System.getProperty("user.home") + "/.aocache/aoconfig.properties";
    private static final String ENV_CONFIG_PATH = "AOCACHE_CONFIG_PROPERTIES";
    
    private static final Properties loadedProperties;

    public static final boolean debugOutput;
    public static final boolean debugOutputDetail;
    public static final boolean usingLoadedJoinPoints;
    public static final boolean override;
    public static final boolean outputAroundCost;
    public static final boolean recordStats;
    public static final boolean throwWrapException;
    
    static {
        loadedProperties = loadProperties();
        debugOutput = getBoolean("logger.debugOutput", false);
        debugOutputDetail = getBoolean("logger.debugOutputDetail", false);
        usingLoadedJoinPoints = getBoolean("usingLoadedJoinPoints", false);
        override = getBoolean("logger.override", false);
        outputAroundCost = getBoolean("logger.outputAroundCost", false);
        recordStats = getBoolean("recordStats", false);
        throwWrapException = getBoolean("protections.throwWrapException", false);
    }
    
    private static Properties loadProperties() {
        Properties props = new Properties();
        
        /** 尝试加载默认配置文件 */
        loadPropertiesFromFile(props, DEFAULT_CONFIG_PATH);
        
        /** 尝试加载环境变量指定的配置文件 */
        String envPath = System.getenv(ENV_CONFIG_PATH);
        if (envPath != null && !envPath.trim().isEmpty()) {
            loadPropertiesFromFile(props, envPath);
        }
        return props;
    }
    
    private static void loadPropertiesFromFile(Properties props, String filePath) {
        Path configPath = Paths.get(filePath);
        File configFile = configPath.toFile();
        if (Files.isRegularFile(configPath) && Files.isReadable(configPath)) {
            try (FileInputStream fis = new FileInputStream(configFile)) {
                props.load(fis);
            } catch (IOException e) {
                AocacheLogger.DEFAULT.log("WARNING:Failed to load config file: " + configFile.getAbsolutePath(), e);
            }
        } else if (Files.exists(configPath)) {
            AocacheLogger.DEFAULT.log("WARNING:Config file not readable: " + configFile.getAbsolutePath());
        } else if (filePath.equals(DEFAULT_CONFIG_PATH)) {
            // Create default config file if it doesn't exist
        	configFile.getParentFile().mkdirs();
			try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(configFile),
					StandardCharsets.UTF_8)) {
				writer.write("# AoCache Configuration\n");
				writer.write("# debugOutput: 是否启用调试输出(默认:false)\n");
				writer.write("#logger.debugOutput=false\n");
				writer.write("# debugOutputDetail: 是否输出详细调试信息(默认:false)\n");
				writer.write("#logger.debugOutputDetail=false\n");
				writer.write("# override: 是否启用注解定义的日志配置覆盖全局配置(默认:false)\n");
				writer.write("#logger.override=false\n");
				writer.write("# usingLoadedJoinPoints: 是否使用加载的切入点配置(默认:false)\n");
				writer.write("#usingLoadedJoinPoints=false\n");
				writer.write("# outputAroundCost: 是否输出环绕方法耗时(默认:false)\n");
				writer.write("#logger.outputAroundCost=false\n");
				writer.write("# PerformanceProfiler配置\n");
				writer.write("# profiler.enableLog: 是否启用性能分析(默认:false)\n");
				writer.write("#profiler.enableLog=false\n");
				writer.write("# profiler.logFolder: 缓存性能分析器的日志文件夹路径,默认:${user.home}/.aocache/profiler\n");
				writer.write("#profiler.logFolder=\n");
				writer.write("# profiler.flushInterval: 性能分析日志写入间隔时间(毫秒),默认:10000ms\n");
				writer.write("#profiler.flushInterval=10000\n");
				writer.write("# profiler.cleanLogFile: 是否在启动时清理旧的性能分析日志文件(默认:false)\n");
				writer.write("#profiler.cleanLogFile=false\n");
                writer.write("# profiler.logPrefix: 性能分析日志文件前缀,(默认:aocache)\n");
				writer.write("#profiler.logPrefix=aocache\n");
				writer.write("# recordStats: 是否启用缓存实时统计(默认:false)\n");
				writer.write("#recordStats=false\n");
				writer.write("# protections.throwWrapException: 是否抛出包装异常(默认:false)\n");
				writer.write("#protections.throwWrapException=false\n");
				writer.close();
				
			} catch (IOException e) {
				AocacheLogger.DEFAULT.log("WARNING:"+e.getMessage(), e);
			}
        }
    }


    /**
     * 获取配置值，优先从系统属性中读取，其次从配置文件读取
     * 
     * @param key 配置键名，不需要包含"aocache."前缀
     * @return 配置值，优先返回系统属性值(aocache.${key})，
     *         如果系统属性未设置则返回配置文件中的值，
     *         都未设置则返回null
     */
    private static String getConfigValue(String key) {
        String sysValue = System.getProperty("aocache." + key);
        return sysValue != null ? sysValue : loadedProperties.getProperty(key);
    }

    /**
     * 从配置中获取Boolean值
     * @param key 配置键
     * @param defaultValue 默认值(可为null)
     * @return 配置值或默认值
     */
    public static Boolean getBoolean(String key, Boolean defaultValue) {
        String value = getConfigValue(key);
        return value != null ? Boolean.parseBoolean(value) : defaultValue;
    }

    public static Boolean getBoolean(String key) {
        return getBoolean(key, null);
    }

    /**
     * 从配置中获取Integer值
     * @param key 配置键
     * @param defaultValue 默认值(可为null)
     * @return 配置值或默认值
     */
    public static Integer getInteger(String key, Integer defaultValue) {
        String value = getConfigValue(key);
        if (value == null) {
        	return defaultValue;
        }
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    public static Integer getInteger(String key) {
        return getInteger(key, null);
    }

    /**
     * 从配置中获取Long值
     * @param key 配置键
     * @param defaultValue 默认值(可为null)
     * @return 配置值或默认值
     */
    public static Long getLong(String key, Long defaultValue) {
        String value = getConfigValue(key);
        if (value == null) {
        	return defaultValue;
        }
        try {
            return Long.parseLong(value);
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    public static Long getLong(String key) {
        return getLong(key, null);
    }

    /**
     * 从配置中获取String值
     * @param key 配置键
     * @param defaultValue 默认值(可为null)
     * @return 配置值或默认值
     */
    public static String getString(String key, String defaultValue) {
        String value = getConfigValue(key);
        return value != null ? value : defaultValue;
    }

    public static String getString(String key) {
        return getString(key, null);
    }

    /**
     * 从配置中获取Class对象
     * @param key 配置键
     * @param defaultValue 默认值(可为null)
     * @return 配置值或默认值
     */
    public static Class<?> getClass(String key, Class<?> defaultValue) {
        String className = getConfigValue(key);
        if (className == null){
            return defaultValue;
        } 
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            return defaultValue;
        }
    }

    public static Class<?> getClass(String key) {
        return getClass(key, null);
    }

}